package async

import (
	"JK-Junior-Go-Engineer-Camp/webook/internal/repository"
	"JK-Junior-Go-Engineer-Camp/webook/internal/service/sms"
	"JK-Junior-Go-Engineer-Camp/webook/pkg/logger"
	"context"
	"time"
)

// AsyncSMSService 短息服务的装饰器-同步转异步
type AsyncSMSService struct {
	svc  sms.Service
	repo repository.AsyncSmsRepository // 存储需要异步发送的短信
	l    logger.LoggerV1
}

func NewAsyncSMSService(svc sms.Service, repo repository.AsyncSmsRepository,
	l logger.LoggerV1) *AsyncSMSService {
	res := &AsyncSMSService{svc: svc, repo: repo, l: l}
	// 开启异步发送短信
	go func() { res.StartAsyncCycle() }()
	return res
}

// StartAsyncCycle 开启异步发送短信
// 原理：这是最简单的抢占式调度
func (s *AsyncSMSService) StartAsyncCycle() {
	// 这里我们没有设计退出机制，是因为没啥必要。
	// 程序停止的时候，自然就停止了
	for {
		s.AsyncSend()
	}
}

// AsyncSend 异步发送短信
func (s *AsyncSMSService) AsyncSend() {
	timeoutCtx, cancelFunc := context.WithTimeout(context.Background(), time.Second)
	// 抢占式发送一条等待中的异步短信
	asyncSmsDomain, err := s.repo.PreemptWaitingSMS(timeoutCtx)
	cancelFunc()
	switch err {
	case nil:
		// 1. 尝试发送异步短信
		timeoutCtx, cancelFunc = context.WithTimeout(context.Background(), time.Second)
		defer cancelFunc()
		err = s.svc.Send(timeoutCtx, asyncSmsDomain.TplId,
			asyncSmsDomain.Args, asyncSmsDomain.Numbers...)
		if err != nil {
			// // 啥也不需要干
			s.l.Error("执行异步发送短信失败",
				logger.Error(err),
				logger.Int64("id", asyncSmsDomain.Id),
			)
		}
		// 2. 告诉 repository 我这一次的执行结果，更新短信发送状态
		res := err == nil
		err = s.repo.ReportScheduleResult(timeoutCtx, asyncSmsDomain.Id, res)
		if err != nil {
			s.l.Error("执行异步发送短信成功，但是更新短信发送状态失败",
				logger.Error(err),
				logger.Bool("res", res),
				logger.Int64("id", asyncSmsDomain.Id),
			)
		}
	case repository.ErrWaitingSMSNotFound: // 没有等待发送的异步短信
		// 睡一秒。这个你可以自己决定
		time.Sleep(time.Second)
	default:
		// 到这里，正常来说应该是数据库出现了问题
		// 但是为了尽量运行，还是要继续的
		// 你可以稍微睡眠，也可以不睡眠
		// 睡眠的话可以帮你规避掉短时间的网络抖动问题
		s.l.Error("抢占式异步发送短信任务失败",
			logger.Error(err),
		)
		time.Sleep(time.Second)
	}
}

// Send 实现接口方法-发送短信
func (s *AsyncSMSService) Send(ctx context.Context, templateId string, args []string, number ...string) error {
	//TODO implement me
	panic("implement me")
}

// 是否需要异步发送短信
func (s *AsyncSMSService) needAsync() bool {
	// todo:这边就是我们需要设计的，各种判定要不要触发异步的方案
	return true
}
